import datetime
import logging
import time
from decimal import Decimal
import calendar
import json
from math import radians, cos, sin, asin, sqrt

import pymongo
from bson import ObjectId
from sqlalchemy import asc
from sqlalchemy import desc
from sqlalchemy.orm import class_mapper

logger = logging.getLogger("wechat")


class ApiInspectError(Exception):
    """接口参数检查错误时抛异常"""


def object_to_dict(obj, found=None):
    if found is None:
        found = set()
    if not isinstance(obj, type):
        columns = []
        for o in dir(obj):
            if o.startswith("_"):
                continue
            elif str(type(getattr(obj, o))) in ["<class 'builtin_function_or_method'>", "<class 'method'>"]:
                continue
            else:
                columns.append(o)
        pass
    else:
        mapper = class_mapper(obj.__class__)
        columns = [column.key for column in mapper.columns]

    def get_key_value(c):
        if isinstance(getattr(obj, c), datetime.datetime):
            return c, getattr(obj, c).strftime("%Y-%m-%d %H:%M:%S")
        elif isinstance(getattr(obj, c), Decimal):
            return c, str(getattr(obj, c))
        elif isinstance(getattr(obj, c), float):
            return c, str(getattr(obj, c))
        else:
            return c, getattr(obj, c)

    out = dict(map(get_key_value, columns))
    if not isinstance(obj, type):
        pass
    else:
        for name, relation in mapper.relationships.items():
            if relation not in found:
                found.add(relation)
                related_obj = getattr(obj, name)
                if related_obj is not None:
                    if relation.uselist:
                        out[name] = [object_to_dict(child, found) for child in related_obj]
                    else:
                        out[name] = object_to_dict(related_obj, found)
    return out


def mongo_to_dict(obj):
    if isinstance(obj.get("_id"), ObjectId):
        obj["_id"] = str(obj["_id"])
        if isinstance(obj.get("create_time"), datetime.datetime):
            obj["create_time"] = obj["create_time"].strftime("%Y-%m-%d %H:%M:%S")
    return obj


def get_command(num):
    """
    生成口令
    :param num:
    :return:
    """
    if not isinstance(num, int):
        num = int(num)
    base_num = 99999  # 口令处理基数
    suf_list = [0, 1, 2, 3, 5, 6, 8, 9] # 尾数值列表
    pre_code = str(base_num - num) # 口令实际数值
    suf_code = suf_list[sum(int(num) for num in str(num)) % 8] # 尾数校验值(规则：各位数之和对8取余为下标在列表取值)
    code = f"{pre_code}{suf_code}"
    return code


class ApiHelper(object):
    @staticmethod
    def inspect_common_params(page_index, page_size):
        if int(page_index) < 0:
            raise ApiInspectError("页号错误")
        if not (1 <= int(page_size) <= 1000):
            raise ApiInspectError("每页条数只能在1~1000之间")

    @staticmethod
    def get_time_range(time_range):
        time_arr = time_range.split("~")
        start_time = time_arr[0]
        end_time = time_arr[1]

        start_time = datetime.datetime.fromtimestamp(time.mktime(time.strptime(start_time, "%Y-%m-%d %H:%M:%S")))
        end_time = datetime.datetime.fromtimestamp(time.mktime(time.strptime(end_time, "%Y-%m-%d %H:%M:%S")))
        return start_time, end_time

    @staticmethod
    def get_per_month_days(year, month):
        """根据年份月份获取当月天数"""
        year = int(year)
        month = int(month)
        month_range = calendar.monthrange(year, month)
        days = month_range[1]
        return days

    @staticmethod
    def get_date_range(date_range):
        date_arr = date_range.split("~")
        start_date = date_arr[0]
        end_date = date_arr[1]
        return start_date, end_date

    @staticmethod
    def merge_list_data(items, total_count, page_index=0, page_size=10):
        ApiHelper.inspect_common_params(page_index, page_size)

        data = dict(
            items=items,
            total_count=total_count,
            page_index=page_index,
            page_size=page_size,
        )
        return data

    @staticmethod
    def get_sql_sort_params(sort_list):
        sort_params = list()
        for s in sort_list:
            sort_method = s[1]
            if sort_method == "asc":
                sort_params.append(asc(s[0]))
            elif sort_method == "desc":
                sort_params.append(desc(s[0]))
        return sort_params

    @staticmethod
    def get_mongo_sort_params(sort_list):
        sort_params = list()
        for s in sort_list:
            sort_method = s[1]
            if sort_method == "asc":
                sort_params.append((s[0], pymongo.ASCENDING))
            elif sort_method == "desc":
                sort_params.append((s[0], pymongo.DESCENDING))
        return sort_params

    @staticmethod
    def get_unit(unit):
        """
        单位规则：
        不足三位数： 支
        等于三位数： 百位数是 1=套 2=个 3=台 4=张 后两位数字代表单位数量, 如404表示4张
        :param unit:
        :return:
        """
        unit = str(unit)
        if len(unit) < 3:
            suf = "支"
        else:
            if unit.startswith("1"):
                suf = "套"
            elif unit.startswith("2"):
                suf = "个"
            elif unit.startswith("3"):
                suf = "台"
            elif unit.startswith("4"):
                suf = "张"
        unit = suf if int(unit[-2:]) == 1 else f"{int(unit[-2:])}{suf}"
        return unit

    @staticmethod
    def geocode(address):
        """
        使用高德API获取地址经纬度
        :param address:
        :return:
        """
        key = "dafba73e8417597d8aa551134324d194"
        par = {"address": address, "key": key}
        base = 'http://restapi.amap.com/v3/geocode/geo'
        import requests
        response = requests.get(base, par)
        answer = response.json()
        gps = answer['geocodes'][0]['location'].split(",")
        lng=gps[0]
        lat=gps[1]
        return lng, lat

    @staticmethod
    def geodistance(lng1, lat1, lng2, lat2):
        """
        根据经纬度计算两点距离：km
        :param lng1:
        :param lat1:
        :param lng2:
        :param lat2:
        :return:
        """
        lng1, lat1, lng2, lat2 = map(Decimal, [lng1, lat1, lng2, lat2])
        lng1, lat1, lng2, lat2 = map(radians, [lng1, lat1, lng2, lat2])
        dlon = lng2 - lng1
        dlat = lat2 - lat1
        a = sin(dlat / 2) ** 2 + cos(lat1) * cos(lat2) * sin(dlon / 2) ** 2
        distance = 2 * asin(sqrt(a)) * 6371
        return "%.2f" % distance

    @staticmethod
    def check_is_work_time(work_time, work_switch):
        """
        检查当前时间是否在设定的时间范围内
        :param work_time:
        :param work_switch:
        :return:
        """
        today = datetime.datetime.now().weekday() + 1
        if work_switch:
            if today in work_time["day"]:
                am_list = work_time["am"].split("-")
                am_start = int(am_list[0].replace(":", ""))
                am_end = int(am_list[1].replace(":", ""))
                pm_list = work_time["pm"].split("-")
                pm_start = int(pm_list[0].replace(":", ""))
                pm_end = int(pm_list[1].replace(":", ""))
                now = int(time.strftime("%H%M"))
                if (am_start<=now<=am_end) or (pm_start<=now<=pm_end):
                    is_work = 1
                else:
                    is_work = 0
            else:
                is_work = 0
        else:
            is_work = 0

        return is_work

    @staticmethod
    def service_fee(total_fee):
        """计算拼柜订单服务费用"""
        if total_fee < 1000:
            rate = 0 # 0.3
        elif total_fee < 3000:
            rate = 0 # 0.25
        elif total_fee < 5000:
            rate = 0 # 0.2
        else:
            rate = 0 # 0.15

        return (Decimal(rate) * total_fee).quantize(Decimal("0.00"))